小知识
...

数据类型
...

java分为简单数据类型和复合类型,简单类型为整数,浮点,字符,布尔;复合类型为类,接口,数组。

简单类型传参是值传递,复合类型是引用传递

静态代码块
...

静态代码块在类被装载时执行,且只执行一次。通常用来做初始化

重载和覆写
...

覆写:方法名,参数,返回类型都相同

重载:方法名相同,参数不同

重载是同一个类中不同的方法,覆写是子类和父类中相同方法的不同实现

抽象类中如果有抽象方法,则具体子类必须对其进行覆写

接口的实现类必须覆写接口的全部抽象方法

方法前有final修饰则不能在子类覆写

抽象类和接口
...

抽象类:用abstract修饰的类,不能创建对象,可以声明并引用其子类对象,非抽象方法可以调用抽象方法,抽象子类不能有父类同名的抽象方法,abstract不能和final修饰同一个类,不能和private,static,final,native修饰同一个方法

接口:方法说明的集合,也可以继承父接口。

抽象类有构造方法,接口没有

抽象类可以有抽象方法和非抽象方法,接口只有抽象方法,且不需要abstract修饰,即为等效

一个类只能继承一个父类,但可以实现多个接口

image-20221225003430972

StringBuilder StringBuffer String
...

image-20221225004620651

StringBuilder部分语法
...

public StringBuffer append(String s)
将指定的字符串追加到此字符序列。

public StringBuffer reverse()
将此字符序列用其反转形式取代。

public delete(int start, int end)
移除此序列的子字符串中的字符。

public insert(int offset, int i)
int 参数的字符串表示形式插入此序列中。

insert(int offset, String str)
str 参数的字符串插入此序列中。

replace(int start, int end, String str)
使用给定 String 中的字符替换此序列的子字符串中的字符

String substring(int start[, int end])
返回一个新的 String,它包含此序列当前所包含的字符子序列。

不同集合类的比较
...

image-20221225222346444

image-20221225223417024

image-20221225222418740

image-20221225223448127

image-20221225223527181

IDEA
...

  1. 资源目录创建多级需要用/,不要用.

多线程
...

创建线程方法
...

  1. 继承Thread类

    public MyThread extends Thread{
    	public void run(){}
    	public static void main(String args){
    		new MyThread().start();
    	}
    }
    
  2. 实现Runnable接口

    public MyThread implements Runnable{
        public void run(){}
        public static void main(String args){
            new Thread(new MyThread()).start();
        }
    }
    

传参
...

创建类对象时作为参数传入成员属性

image-20221225124529583

线程加锁(synchronized)
...

保证它修饰的方法或者代码块任意时刻只有一个线程在访问。

三种用法:

image-20221225151354786

synchronized(要加锁的变量){//对代码块加锁
	代码块
}
public synchronized void add(){//对方法加锁
    方法体
}

volatile
...

volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。

synchronized和volatile区别
...

volatile只能修饰变量,且多线程访问时不会出现阻塞;而synchronized可以修饰变量,代码块,方法;synchronized可以保证原子性,常用于操作同步,volatile常用于保证变量可见性。

部分线程操作方法
...

public final void setName(String name)

改变线程名称,使之与参数 name 相同。

public final void setPriority(int priority)

更改线程的优先级。

notifyAll()

唤醒等待资源的所有线程

wait()

线程等待

反射
...

  1. 反射机制允许程序在执行期借助于 ReflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到

  2. 加载完类之后,在堆中就产生了ー个 Classe类型的对象(一个类只有一个 Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个 Class对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射

  3. 相关主要类

    java.lang.Class:代表一个类, Class对象表示某个类加载后在堆中的对象

    java.lang.reflect.Method:代表类的方法, Method对象表示某个类的方法

    java.lang.reflect.Field:代表类的成员变量

    java.lang.reflect.Constructor:代表类的构造方法

  4. eg.

    		//读配置文件
            Properties properties = new Properties();
            properties.load(new FileInputStream("code\\t26_reflection\\class.properties"));
            String classpath = properties.get("classpath").toString();
            String method = properties.get("method").toString();
    
            //反射机制获得类调方法
            Class aClass = Class.forName(classpath);
            //获得要加载的类的对象实例
            Object o = aClass.newInstance();
            //o的运行类型
            System.out.println(o.getClass());
            //java.lang.reflect.Method:代表类的方法, Method对象表示某个类的方法
            Method method1 = aClass.getMethod(method);
            //方法.invoke(对象)来调用方法
            method1.invoke(o);
            //java.lang.reflect.Field:代表类的成员变量
            //getField不能得到私有属性
            Field numField = aClass.getField("num");
            //成员变量对象.get(对象)
            System.out.println(numField.get(o));
            //java.lang.reflect.Constructor:代表类的构造方法
            //可以指定构造器参数类型
            Constructor constructor = aClass.getConstructor(int.class);
            Object o1 = constructor.newInstance(1);
            method1.invoke(o1);
    
  5. 反射调用优化:关闭访问检查

    Method和 Field、 Constructor对象都有 setAccessible()方法

    setAccessible作用是启动和禁用访问安全检查的开关

    参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为 false则表示反射的对象执行访问检查

  6. Class类常用方法

    image-20220817150255629

  7. 在不同阶段拿到Class类对象

    • 代码阶段:Class.forName()
    • 加载阶段:类.class
    • 运行阶段:对象.getClass()
  8. 类加载

    • image-20220817152909165
  9. 通过反射获取类的结构信息

    image-20220817154842695

image-20220817155017633

image-20220817155944312

image-20220817160040304

  1. 反射构造对象

    image-20220817162653594

    当构造器私有时,可以用getDeclearedConstructor(形参class)获取构造器,对构造器使用setAccessable(true)暴破

    此时可以用该私有构造器构造对象

  2. 反射访问类中成员

    image-20220817162356840

image-20220817162558378

内部类
...

  • 在一个类内定义一个类

    public class A{
    	修饰符 class B{
    	
    	}
    }
    
  • 访问特点

    1.内部类可以直接访问外部类的成员,包括私有

    2.外部类要访问内部类的成员必须先创建对象

成员内部类
...

  • 在类的成员位置

  • 在外界调用(仅限内部类为public)

    Outer.Inner a = new Outer().new Inner();
    
  • 外部类内可以调用private内部类,故可以用外部类的方法调内部类

局部内部类
...

  • 定义在方法中,外界无法直接调用,需要在方法内创建对象使用。局部内部类可以访问外部类的成员和方法内的变量

    public class Outer {
        
        private int num = 10;
        
        public void method(){
            int num2 = 20;
            class Inner {
                public void show(){
                    System.out.println(num);
                    System.out.println(num2);
                }
            }
        }
    }
    

匿名内部类
...

  • 一个继承了某类或实现了某接口的子类匿名对象

    public class Outer {
        
        public void method(){
            new Inter(){
                @Override
                public void method() {
                    System.out.println("inner");
                }
            };
        }
    }
    

异常
...

概述
...

image-20221012212653733

  • Error: 严重问题,不需要处理
  • Exception:异常类,程序本身可以处理的问题
  • RuntimeException:在编译期不检查的,出现问题后需要回来修改代码
  • 非RuntimeException:编译期必须处理,否则不能通过编译

异常处理
...

  • 格式:

    try{
    	可能出现异常的代码
    }catch(异常类名 变量名){
    	异常处理代码
    }finally{
        最终执行代码
    }
    

    e.printStackTrace():把异常的错误信息输出在控制台

    e.toString():返回简短描述

    e.getMessage():返回此throwable的详细消息字符串

异常分类
...

  • 编译时异常(受检异常):必须显示处理,否则程序就会发生错误,无法通过编译
  • 运行时异常(非受检异常):无需显示处理,也可以和编译时异常一样处理

throws
...

  • 格式

    throws 异常类名
    

    跟在方法的括号后面

  • 编译时异常必须要进行处理,如果用throws将来谁调用谁处理

  • 运行时异常可以不处理,出现问题后需要修改代码

自定义异常
...

  • 定义一个类继承Exception

  • 添加无参和带参构造

    super(msg);
    
  • 使用时可以在throw新异常类对象时带参,表示问题

IO流
...

  1. 入门

    • IO,即in和out,也就是输入和输出,指应用程序和外部设备之间的数据传递,常见的外部设备包括文件、管道、网络连接。

      Java 中是通过流处理IO 的,那么什么是流?

      流(Stream),是一个抽象的概念,是指一连串的数据(字符或字节),是以先进先出的方式发送信息的通道。

      当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。这时候你就可以想象数据好像在这其中“流”动一样。

      一般来说关于流的特性有下面几点:

      1. 先进先出:最先写入输出流的数据最先被输入流读取到。
      2. 顺序存取:可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile除外)
      3. 只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
    • IO流分类

      1. 按数据流方向:输入流、输出流

        读取文件是输入流,写文件是输出流

      2. 按处理数据单位:字节流、字符流

        字节流一般用来处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,但不能处理图像视频等非文本文件。用一句话说就是:字节流可以处理一切文件,而字符流只能处理纯文本文件。

      3. 按功能:节点流、处理流(缓冲字节流)

        节点流:直接操作数据读写的流类,比如FileInputStream

        处理流:对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如BufferedInputStream(缓冲字节流)

  2. IO流对象

    1. File类

      • File类是用来操作文件的类,但不能操作文件里的数据

      • 构造方法

        方法名说明
        File(File parent, String child)根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
        File(String pathname)通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
        File(String parent, String child)根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
        File(URI uri)通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。
      • 常用方法

        方法说明
        createNewFile()当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
        delete()删除此抽象路径名表示的文件或目录。
        exists()测试此抽象路径名表示的文件或目录是否存在。
        getAbsoluteFile()返回此抽象路径名的绝对路径名形式。
        getAbsolutePath()返回此抽象路径名的绝对路径名字符串。
        length()返回由此抽象路径名表示的文件的长度。
        mkdir()创建此抽象路径名指定的目录。
      • 使用示例

        public class FileTest {
        	public static void main(String[] args) throws IOException {
        		File file = new File("C:/Mu/fileTest.txt");
        
        		// 判断文件是否存在
        		if (!file.exists()) {
        			// 不存在则创建
        			file.createNewFile();
        		}
        		System.out.println("文件的绝对路径:" + file.getAbsolutePath());
        		System.out.println("文件的大小:" + file.length());
        
        		// 刪除文件
        		file.delete();
        	}
        }
        
    2. 字节流

      • InputStream实现子类

        1. InputStream:InputStream是所有字节输入流的抽象基类,前面说过抽象类不能被实例化,实际上是作为模板而存在的,为所有实现类定义了处理输入流的方法。
        2. FileInputSream:文件输入流,一个非常重要的字节输入流,用于对文件进行读取操作。
        3. PipedInputStream:管道字节输入流,能实现多线程间的管道通信。
        4. ByteArrayInputStream:字节数组输入流,从字节数组(byte[])中进行以字节为单位的读取,也就是将资源文件都以字节的形式存入到该类中的字节数组中去。
        5. FilterInputStream:装饰者类,具体的装饰者继承该类,这些类都是处理类,作用是对节点类进行封装,实现一些特殊功能。
        6. DataInputStream:数据输入流,它是用来装饰其它输入流,作用是“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。
        7. BufferedInputStream:缓冲流,对节点流进行装饰,内部会有一个缓存区,用来存放字节,每次都是将缓存区存满然后发送,而不是一个字节或两个字节这样发送,效率更高。
        8. ObjectInputStream:对象输入流,用来提供对基本数据或对象的持久存储。通俗点说,也就是能直接传输对象,通常应用在反序列化中。它也是一种处理流,构造器的入参是一个InputStream的实例对象。
    3. 字符流

      • Reader实现子类

        1. InputStreamReader:从字节流到字符流的桥梁(InputStreamReader构造器入参是FileInputStream的实例对象),它读取字节并使用指定的字符集将其解码为字符。它使用的字符集可以通过名称指定,也可以显式给定,或者可以接受平台的默认字符集。
        2. BufferedReader:从字符输入流中读取文本,设置一个缓冲区来提高效率。BufferedReader是对InputStreamReader的封装,前者构造器的入参就是后者的一个实例对象。
        3. FileReader:用于读取字符文件的便利类,new FileReader(File file)等同于new InputStreamReader(new FileInputStream(file, true),"UTF-8"),但FileReader不能指定字符编码和默认字节缓冲区大小。
        4. PipedReader :管道字符输入流。实现多线程间的管道通信。
        5. CharArrayReader:从Char数组中读取数据的介质流。
        6. StringReader :从String中读取数据的介质流。
  3. IO流方法

    1. 字节流方法

      1. 字节输入流InputStream()主要方法

        • read() :从此输入流中读取一个数据字节。
        • read(byte[] b) :从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
        • read(byte[] b, int off, int len) :从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
        • close():关闭此输入流并释放与该流关联的所有系统资源。
      2. 字节输出流OutputStream主要方法

        • write(byte[] b) :将 b.length 个字节从指定 byte 数组写入此文件输出流中。
        • write(byte[] b, int off, int len) :将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
        • write(int b) :将指定字节写入此文件输出流。
        • close() :关闭此输入流并释放与该流关联的所有系统资源。
    2. 字符流方法

      1. 字符输入流Reader主要方法

        • read():读取单个字符。
        • read(char[] cbuf) :将字符读入数组。
        • read(char[] cbuf, int off, int len) : 将字符读入数组的某一部分。
        • read(CharBuffer target) :试图将字符读入指定的字符缓冲区。
        • flush() :刷新该流的缓冲。
        • close() :关闭此流,但要先刷新它。
      2. 字符输出流Writer主要方法

        • write(char[] cbuf) :写入字符数组。
        • write(char[] cbuf, int off, int len) :写入字符数组的某一部分。
        • write(int c) :写入单个字符。
        • write(String str) :写入字符串。
        • write(String str, int off, int len) :写入字符串的某一部分。
        • flush() :刷新该流的缓冲。
        • close() :关闭此流,但要先刷新它。
    3. 字符缓冲流方法

      • BufferedWriter类newLine():写入一个行分隔符。这个方法会自动适配所在系统的行分隔符。
      • BufferedReader类readLine() :读取一个文本行。

网络编程
...

  1. 入门

    1. 三要素

      • ip地址

        • IPv4:是给每个连接在网络上的主机分配个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例做如个采用二进制形式的P地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号""分隔不同的字节。于是,上面的P地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法",这显然比1和0容易记忆得多
        • IPv6:由于互联网的蓬戟发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题
      • 端口

        • 端口:设备上应用程序的唯一标识
        • 端口号:用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
      • 协议

        • UDP

          • 用户数据报协议
          • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
          • 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
        • TCP

          • 传输控制协议

          • TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”

          • 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠

            第一次握手,客户端向服努器端发岀连接请求,等待服器确认

            第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求

            第三次握手,客户端再次向肜务器端发送确认信息,确认连接

    2. InetAddress类使用

      方法名说明
      static InetAddress getByName(String host)确定主机名称的ip地址。主机名称可以是机器名称也可以是ip地址
      String getHostName()获取此ip地址的主机名
      String getHostAddress()返回文本显示中的ip地址字符串
  2. UDP通信程序

    1. UDP发送数据

      1. 创建发送端Socket对象(DatagramSocket)

      2. 创建数据,并把数据打包(DatagramPacket)

      3. 调用DatagramSocket对象的方法发送数据

      4. 关闭发送端

        import java.io.IOException;
        import java.net.*;
        
        public class UdpTest1 {
            public static void main(String[] args) throws IOException {
                DatagramSocket ds = new DatagramSocket();
                byte[] bytes = "hello".getBytes();
                DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("localhost"), 25565);
                ds.send(packet);
        
        
                ds.close();
            }
        }
        
    2. UDP接收数据

      1. 创建接收端的Socket对象(DatagramSocket)

      2. 创建一个数据包,用于接收数据

      3. 调用DatagramSocket对象的方法接收数据

      4. 解析数据包,并把数据在控制台显示

      5. 关闭接收端

        import java.io.IOException;
        import java.net.DatagramPacket;
        import java.net.DatagramSocket;
        
        public class UdpTest2 {
            public static void main(String[] args) throws IOException {
                DatagramSocket socket = new DatagramSocket(25565);
                byte[] bytes = new byte[1024];
                DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
                socket.receive(packet);
        
                String info = new String(packet.getData(), 0, packet.getLength());
                System.out.println(info);
                socket.close();
        
            }
        }
        
  3. TCP通信程序

    1. TCP通信原理:TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个 Socket时象,从而在通信的两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过 Socket产生IO流来进行网络通信。Java为客户端提供了 Socket类,为服务器端提供了 ServerSocket类

    2. TCP发送数据

      1. 创建客户端的Socket对象

      2. 获取输出流,写数据

      3. 释放资源

        import java.io.IOException;
        import java.io.OutputStream;
        import java.net.Socket;
        
        public class TcpTest1 {
            public static void main(String[] args) throws IOException {
                Socket socket = new Socket("localhost", 20000);
                OutputStream os = socket.getOutputStream();
                os.write("hello".getBytes());
                
                os.close();
                
            }
        }
        
    3. TCP接收数据

      1. 创建服务端的Socket(ServerSocket)

      2. 获取输入流,读数据

      3. 释放资源

        import java.io.IOException;
        import java.io.InputStream;
        import java.net.ServerSocket;
        import java.net.Socket;
        
        public class TcpTest2 {
            public static void main(String[] args) throws IOException {
                ServerSocket socket = new ServerSocket(20000);
                Socket socket1 = socket.accept();
        
                InputStream is = socket1.getInputStream();
                byte[] bytes = new byte[1024];
                int len = is.read(bytes);
                String s = new String(bytes, 0, len);
                System.out.println(s);
            }
        }
        

图形化
...

Swing如果要做实时更新消息,可以用SwingWorker开线程

Lambda表达式
...

Lambda 表达式主要用来定义行内执行的方法类型接口

格式:

(parameters) -> expression或(parameters) ->{ statements; }

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。

lambda 表达式只能引用标记了 final 的外层局部变量,不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

    public static void main(String args[]) {
        final int num = 1;
        Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
        s.convert(2);  // 输出结果为 3
    }

    public interface Converter<T1, T2> {
        void convert(int i);
    }

方法引用
...

package com.runoob.main;
 
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
 
class Car {
    //Supplier是jdk1.8的接口,这里和lamda一起使用了
    public static Car create(final Supplier<Car> supplier) {
        return supplier.get();
    }
 
    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }
 
    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }
 
    public void repair() {
        System.out.println("Repaired " + this.toString());
    }
}
  • 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:

    final Car car = Car.create( Car::new ); 
    final List< Car > cars = Arrays.asList( car );
    
  • 静态方法引用:它的语法是Class::static_method,实例如下:

    cars.forEach( Car::collide );
    
  • 特定类的任意对象的方法引用:它的语法是Class::method实例如下:

    cars.forEach( Car::repair );
    
  • 特定对象的方法引用:它的语法是instance::method实例如下:

    final Car police = Car.create( Car::new ); 
    cars.forEach( police::follow );
    

函数式接口
...

函数式接口就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

函数式接口可以被隐式转换为 lambda 表达式。

函数式接口实例
...

Predicate 接口是一个函数式接口,它接受一个输入参数 T,返回一个布尔值结果。

该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。

该接口用于测试对象是 true 或 false。

我们可以通过以下实例来了解函数式接口 Predicate 的使用:

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
 
public class Java8Tester {
   public static void main(String args[]){
      List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        
      // Predicate<Integer> predicate = n -> true
      // n 是一个参数传递到 Predicate 接口的 test 方法
      // n 如果存在则 test 方法返回 true
        
      System.out.println("输出所有数据:");
        
      // 传递参数 n
      eval(list, n->true);
        
      // Predicate<Integer> predicate1 = n -> n%2 == 0
      // n 是一个参数传递到 Predicate 接口的 test 方法
      // 如果 n%2 为 0 test 方法返回 true
        
      System.out.println("输出所有偶数:");
      eval(list, n-> n%2 == 0 );
        
      // Predicate<Integer> predicate2 = n -> n > 3
      // n 是一个参数传递到 Predicate 接口的 test 方法
      // 如果 n 大于 3 test 方法返回 true
        
      System.out.println("输出大于 3 的所有数字:");
      eval(list, n-> n > 3 );
   }
    
   public static void eval(List<Integer> list, Predicate<Integer> predicate) {
      for(Integer n: list) {
        
         if(predicate.test(n)) {
            System.out.println(n + " ");
         }
      }
   }
}

默认方法
...

接口可以有实现方法,而且不需要实现类去实现其方法。

public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
   // 静态方法
   static void blowHorn(){
      System.out.println("按喇叭!!!");
   }
}

一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:

public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
}
 
public interface FourWheeler {
   default void print(){
      System.out.println("我是一辆四轮车!");
   }
}

第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:

public class Car implements Vehicle, FourWheeler {
   default void print(){
      System.out.println("我是一辆四轮汽车!");
   }
}

第二种解决方案可以使用 super 来调用指定接口的默认方法:

public class Car implements Vehicle, FourWheeler {
   public void print(){
      Vehicle.super.print();
   }
}

Stream
...

Stream是一个来自数据源的元素队列并支持聚合操作。元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。

image-20230830131011111

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

特性:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

生成流
...

在 Java 8 中, 集合接口有两个方法来生成流:

  • stream() − 为集合创建串行流。
  • parallelStream() − 为集合创建并行流。
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

forEach
...

Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

map
...

map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

filter
...

filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();

limit
...

limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

sorted
...

sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:

Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);

并行(parallel)程序
...

parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

Collectors
...

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
 
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);

统计
...

另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
 
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
 
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());

Optional类
...

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

Optional 类的引入很好的解决空指针异常。